﻿using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;

namespace NForthCore
{
    public class Engine
    {
        private AssemblyBuilder assbuilder;
        private ModuleBuilder module_builder;
        private int method_id = 0;
        private TypeBuilder type_builder;
        private List<NForthCore.Word> words = new List<NForthCore.Word>();
        private List<string> namespaces = new List<string>();

        public FieldInfo DS { get; set; }
        public FieldInfo RS { get; set; }
        public FieldInfo Dictionary { get; set; }

        public Engine()
        {
            namespaces.Add("");
            namespaces.Add("System.");
            namespaces.Add("System.Reflection.");
            namespaces.Add("System.Reflection.Emit.");

            AssemblyName assembly = new AssemblyName("NForthAssembly");
            assbuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assembly, AssemblyBuilderAccess.RunAndSave);

            CreateStacks();
        }

        public void CreateVariable(string word)
        {
            CreateMethod(word);
            DefineField("Value");
            ILGenerator il = DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, DS);
            il.Emit(OpCodes.Ldsfld, GetField("dictionary", "Value"));
            il.Emit(OpCodes.Ldstr, word);
            il.Emit(OpCodes.Ldstr, "Value");
            il.Emit(OpCodes.Call, typeof(NForthCore.Engine).GetMethod("GetField"));
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            FinishMethod();
        }

        public Type CreateMethod(string word)
        {
            string id = (++method_id).ToString();
            module_builder = assbuilder.DefineDynamicModule("Module" + id);
            type_builder = module_builder.DefineType("MethodType" + id, TypeAttributes.Public);

            words.Add(new Word(word, 0, null));
            
            return type_builder;
        }

        public void FinishMethod()
        {
            type_builder.CreateType();
            module_builder.CreateGlobalFunctions();

            string id = method_id.ToString();
            Type type = module_builder.GetType("MethodType" + id);

            module_builder = null;
            type_builder = null;

            Latest.Type = type;
        }

        public void Immediate()
        {
            Latest.Flags |= (int)NForthCore.WordFlags.F_IMMEDIATE;
        }

        public Word Latest
        {
            get { return words[words.Count - 1]; }
        }

        public int GetWordFlags(string word)
        {
            foreach (Word w in words)
                if (w.Name == word)
                    return w.Flags;

            return 0;
        }

        public string[] GetWords()
        {
            string[] array = new string[words.Count];
            for (int i = 0; i < array.Length; i++)
                array[i] = (string)words[i].Name;
            return array;
        }

        public Type GetType(string word)
        {
            for (int i = words.Count - 1; i >= 0; i--)
                if ((string)words[i].Name == word)
                    return (Type)words[i].Type;

            foreach (string ns in namespaces)
            {
                Type t = Type.GetType(ns + word);
                if (t != null)
                    return t;
            }

            return null;
        }

        public FieldInfo GetField(string word, string field)
        {
            Type type = GetType(word);
            if (type != null)
                return type.GetField(field);

            return null;
        }

        public FieldInfo GetNetField(string name)
        {
            int index = name.LastIndexOf('.');
            if (index < 0)
                return null;

            string cs = name.Substring(0, index);
            Type type = GetType(cs);
            if (type == null)
                return null;

            string field = name.Substring(index + 1);
            return type.GetField(field);
        }

        public MethodInfo GetMethod(string word, string method)
        {
            Type type = GetType(word);
            if (type != null)
                return type.GetMethod(method, new Type[] { });

            return null;
        }

        public MethodInfo GetNetMethod(string name)
        {
            int index = name.LastIndexOf('.');
            if (index < 0)
                return null;

            string cs = name.Substring(0, index);
            Type type = GetType(cs);
            if (type == null)
                return null;

            string method = name.Substring(index + 1);
            string[] args = method.Split('`');

            Type[] types = new Type[args.Length - 1];
            for (int i = 0; i < types.Length; i++)
                types[i] = GetType(args[i + 1]);

            MethodInfo method_info = type.GetMethod(args[0], types);
            if (method_info == null)
                method_info = type.GetMethod(args[0]);

            return method_info;
        }

        public MethodBuilder DefineMethod(string name)
        {
            return type_builder.DefineMethod("Execute", MethodAttributes.Public | MethodAttributes.Static, null, null);
        }

        public FieldBuilder DefineField(string name)
        {
            return type_builder.DefineField("Value", typeof(object), FieldAttributes.Static | FieldAttributes.Public);
        }

        public void See(string word)
        {
            string code = Disassembler.Disassemble(GetMethod(word, "Execute"));
            foreach (string w in GetWords())
            {
                Type type = GetType(w);
                code = code.Replace(" " + type.Name + ".", " " + w + ".");
            }
            Console.WriteLine(word + ":\n" + code);
        }

        public void Execute(Stack s, MethodInfo method_info)
        {
            // class instance
            object obj = null;
            if (!method_info.IsStatic)
                obj = s.Pop();

            // method parameters
            ParameterInfo[] parameters = method_info.GetParameters();
            object[] args = new object[parameters.Length];
            for (int i = 0; i < args.Length; i++)
                if (parameters[i].ParameterType == typeof(bool))
                    args[i] = Convert.ToBoolean(s.Pop());
                else
                    args[i] = s.Pop();

            // invoke method
            object result = method_info.Invoke(obj, args);

            // push result
            if (method_info.ReturnType != typeof(void))
                s.Push(result);
        }

        public void CompileLiteral(MethodBuilder mb, object value)
        {
            ILGenerator il = mb.GetILGenerator();
            il.Emit(OpCodes.Ldsfld, DS);

            if (value.GetType() == typeof(int))
            {
                il.Emit(OpCodes.Ldc_I4, (int)value);
                il.Emit(OpCodes.Box, typeof(int));
            }
            else if (value.GetType() == typeof(string))
                il.Emit(OpCodes.Ldstr, (string)value);
            else
                il.Emit(OpCodes.Ldnull);

            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Push"));
        }

        public void CompileMethod(MethodBuilder mb, MethodInfo method)
        {
            ILGenerator il = mb.GetILGenerator();

            if (method.ReturnType != typeof(void))
                il.Emit(OpCodes.Ldsfld, DS);

            if (!method.IsStatic)
            {
                il.Emit(OpCodes.Ldsfld, DS);
                il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));
            }

            foreach (ParameterInfo p in method.GetParameters())
            {
                il.Emit(OpCodes.Ldsfld, DS);
                il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Pop"));

                if (p.ParameterType == typeof(bool))
                {
                    il.Emit(OpCodes.Unbox_Any, typeof(int));
                    il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToBoolean", new Type[] { typeof(int) }));
                }
                else if (p.ParameterType.IsValueType)
                    il.Emit(OpCodes.Unbox_Any, p.ParameterType);
            }

            if (method.IsVirtual)
                il.Emit(OpCodes.Callvirt, method);
            else
                il.Emit(OpCodes.Call, method);

            if (method.ReturnType != typeof(void))
            {
                if (method.ReturnType.IsValueType)
                    il.Emit(OpCodes.Box, method.ReturnType);
                il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Push"));
            }
        }

        public void CompileField(MethodBuilder mb, FieldInfo field)
        {
            mb.GetILGenerator().Emit(OpCodes.Ldtoken, field.DeclaringType);
            CompileMethod(mb, typeof(Type).GetMethod("GetTypeFromHandle"));
            mb.GetILGenerator().Emit(OpCodes.Ldstr, field.Name);
            CompileMethod(mb, typeof(Type).GetMethod("GetField", new Type[] { typeof(string) }));
        }

        public void CreateStacks()
        {
            ILGenerator il;

            // datastack
            CreateMethod("DS");
            DS = DefineField("Value");
            il = DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, DS);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Call, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            FinishMethod();

            CreateMethod("init-datastack");
            il = DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Newobj, typeof(NForthCore.Stack).GetConstructor(new Type[] { }));
            il.Emit(OpCodes.Stsfld, DS);
            il.Emit(OpCodes.Ret);
            FinishMethod();

            // returnstack
            CreateMethod("RS");
            RS = DefineField("Value");
            il = DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, RS);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Call, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            FinishMethod();

            CreateMethod("init-returnstack");
            il = DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Newobj, typeof(NForthCore.Stack).GetConstructor(new Type[] { }));
            il.Emit(OpCodes.Stsfld, RS);
            il.Emit(OpCodes.Ret);
            FinishMethod();

            // create stacks
            GetMethod("init-datastack", "Execute").Invoke(null, null);
            GetMethod("init-returnstack", "Execute").Invoke(null, null);

            // this pointer
            CreateMethod("dictionary");
            Dictionary = DefineField("Value");
            il = DefineMethod("Execute").GetILGenerator();
            il.Emit(OpCodes.Ldsfld, GetField("DS", "Value"));
            il.Emit(OpCodes.Ldsfld, Dictionary);
            il.Emit(OpCodes.Callvirt, typeof(NForthCore.Stack).GetMethod("Push"));
            il.Emit(OpCodes.Ret);
            FinishMethod();
            GetField("dictionary", "Value").SetValue(null, this);
        }
    }
}
